package com.puzzlebazar.client.util;

import com.puzzlebazar.client.puzzleinfo.HistoryInfo;
import com.puzzlebazar.client.puzzleinfo.NewPuzzleInfo;
import com.puzzlebazar.client.puzzleinfo.PuzzleInfo;
import com.puzzlebazar.client.puzzleinfo.PuzzleInfo.Status;
import com.puzzlebazar.client.user.UserInfo;

public class Capabilities {

	/**
	 * Check if the given puzzle can be edited by the given user.
	 * 
	 * IMPORTANT: Keep in sync with CapabilitesServer.canEditPuzzleFilter
	 * 
	 * @param userInfo The user that should have the right to edit this puzzle
	 * @param puzzleInfo The information on the puzzle to check
	 * 
	 * @return True is the puzzle is editable by this user, false otherwise
	 */
	static public boolean canEditPuzzle( UserInfo userInfo, PuzzleInfo puzzleInfo ) {
		if( puzzleInfo.isTutorial() ) 
			return false;
		if( userInfo == null || puzzleInfo.getStatus() != Status.PRIVATE ) 
			return false;
		if( userInfo.isAdministrator() )
			return true;
		return userInfo.getEmail().equals( puzzleInfo.getAuthorEmail() );
	}
	

	/**
	 * Check if the given puzzle can be played by the given user. 
	 *
	 * IMPORTANT: Keep in sync with CapabilitesServer.canPlayPuzzleFilter
	 * 
	 * @param userInfo The user that should have the right to play this puzzle
	 * @param puzzleInfo The information on the puzzle to check
	 * 
	 * @return True is the puzzle is playable by this user, false otherwise
	 */
	static public boolean canPlayPuzzle( UserInfo userInfo, PuzzleInfo puzzleInfo ) {
		if( puzzleInfo.isTutorial() ) 
			return false;		
		if( !puzzleInfo.hasModelInDatastore() )
			return false;
		Status status = puzzleInfo.getStatus();
		if( status == Status.PUBLISHED ||
				status == Status.REVIEW_PUBLISHED )
			return true;
		
		if( userInfo != null && userInfo.isAdministrator() )
			return true;

		if( userInfo == null || !userInfo.getEmail().equals( puzzleInfo.getAuthorEmail() ) )
			return false;

		return  status == Status.PRIVATE ||
				status == Status.IN_REVIEW ||
				status == Status.ACCEPTED;
	}


	/**
	 * Check if the given user can view its own solution to the puzzle.
	 * 
	 * @param userInfo The user that should have the right to view its own solution to this puzzle
	 * @param puzzleInfo The information on the puzzle to check
	 * 
	 * @return True is the user can view its own solution, false otherwise
	 */
	public static boolean canViewOwnSolutionOfPuzzle(UserInfo userInfo,
			PuzzleInfo puzzleInfo) {

		String specificUserEmail = puzzleInfo.getSpecificUserEmail();
		if( !canPlayPuzzle( userInfo, puzzleInfo ) || 
				userInfo == null || specificUserEmail == null ||
				!userInfo.getEmail().equals( specificUserEmail ) )
			return false;
		
		return puzzleInfo.isSolvedByUser();
	}

	
	

	/**
	 * Check if the given puzzle can be continued by the given user. 
	 * 
	 * @param userInfo The user that should have the right to continue this puzzle
	 * @param puzzleInfo The information on the puzzle to check
	 * 
	 * @return True is the puzzle can be continued by this user, false otherwise
	 */
	public static boolean canContinuePuzzle(UserInfo userInfo,
			PuzzleInfo puzzleInfo) {

		String specificUserEmail = puzzleInfo.getSpecificUserEmail();
		if( !canPlayPuzzle( userInfo, puzzleInfo ) || 
				userInfo == null || specificUserEmail == null ||
				!userInfo.getEmail().equals( specificUserEmail ) )
			return false;
		
		return puzzleInfo.isStartedByUser();
	}
		
	
	/**
	 * Check if the given user can load the specified puzzle
	 * 
	 * IMPORTANT: Keep in sync with CapabilitesServer.canLoadPuzzleFilter
	 * 
	 * @param userInfo The user that wants to load a puzzle
	 * @param puzzleInfo The information on the puzzle to load
	 * @return true if the user can load this puzzle, false otherwise
	 */
	public static boolean canLoadPuzzle(UserInfo userInfo, PuzzleInfo puzzleInfo) {
		return canPlayPuzzle(userInfo, puzzleInfo) || canEditPuzzle(userInfo, puzzleInfo);
	}

	/**
	 * Check if the given user can create any kind of new puzzle
	 * 
	 * @param userInfo The user that wants to create a puzzle
	 * @return True if the user can create a puzzle, false otherwise
	 */
	public static boolean canCreatePuzzle(UserInfo userInfo) {
		return userInfo != null;
	}

	/**
	 * Check if the given tutorial can be edited by the given user.
	 * 
	 * IMPORTANT: Keep in sync with CapabilitesServer.canEditTutorialFilter
	 * 
	 * @param userInfo The user that should have the right to edit this tutorial
	 * @param puzzleInfo The information on the tutorial to check
	 * 
	 * @return True is the tutorial is editable by this user, false otherwise
	 */
	static public boolean canEditTutorial( UserInfo userInfo, PuzzleInfo puzzleInfo ) {
		if( !puzzleInfo.isTutorial() ) 
			return false;
		if( userInfo == null || puzzleInfo.getStatus() != Status.PRIVATE ) 
			return false;
		if( userInfo.isAdministrator() )
			return true;
		return userInfo.getEmail().equals( puzzleInfo.getAuthorEmail() );
	}


	/**
	 * Check if the given tutorial can be viewed by the given user. 
	 *
	 * IMPORTANT: Keep in sync with CapabilitesServer.canViewTutorialFilter
	 * 
	 * @param userInfo The user that should have the right to view this tutorial
	 * @param puzzleInfo The information on the tutorial to check
	 * 
	 * @return True is the tutorial is viewable by this user, false otherwise
	 */
	static public boolean canViewTutorial( UserInfo userInfo, PuzzleInfo puzzleInfo ) {
		if( !puzzleInfo.isTutorial() ) 
			return false;		
		if( !puzzleInfo.hasModelInDatastore() )
			return false;
		Status status = puzzleInfo.getStatus();
		return( status == Status.PUBLISHED ||
				status == Status.REVIEW_PUBLISHED );
	}

	/**
	 * Check if the given user can load the specified tutorial
	 * 
	 * @param userInfo The user that wants to load a tutorial
	 * @param puzzleInfo The information on the tutorial to load
	 * @return true if the user can load this tutorial, false otherwise
	 */
	public static boolean canLoadTutorial(UserInfo userInfo,
			PuzzleInfo puzzleInfo) {
		return canViewTutorial( userInfo, puzzleInfo ) || canEditTutorial( userInfo, puzzleInfo );
	}


	/**
	 * Check if the given user can create any kind of new tutorial
	 * 
	 * @param userInfo The user that wants to create a tutorial
	 * @return True if the user can create a tutorial, false otherwise
	 */
	public static boolean canCreateTutorial(UserInfo userInfo) {
		return userInfo != null;
	}


	/**
	 * Check if the given user can create a puzzle or tutorial with specific characteristics
	 * 
	 * @param userInfo  The user that wants to create a puzzle
	 * @param newPuzzleInfo  The information on the puzzle or tutorial to create
	 * @return true if the user can create this puzzle, false otherwise
	 */
	public static boolean canCreatePuzzleOrTutorial(UserInfo userInfo, NewPuzzleInfo newPuzzleInfo) {
		
		if( userInfo == null || newPuzzleInfo.getAuthorEmail() == null )
			return false;

		if( userInfo.isAdministrator() ) 
			return true;
		
		return userInfo.getEmail().equals( newPuzzleInfo.getAuthorEmail() );
	}
	

	/**
	 * Check if the given user can rename the specified puzzle
	 * 
	 * @param userInfo The user that wants to rename a puzzle
	 * @param puzzleInfo The information on the puzzle to rename
	 * @return true if the user can rename this puzzle, false otherwise
	 */
	public static boolean canRenamePuzzleOrTutorial(UserInfo userInfo, PuzzleInfo puzzleInfo) {
		return canEditPuzzle(userInfo, puzzleInfo) || canEditTutorial(userInfo,puzzleInfo);
	}


	/**
	 * Check if the given user can delete the specified puzzle
	 * 
	 * @param userInfo The user that wants to delete a puzzle
	 * @param puzzleInfo The information on the puzzle to delete
	 * @return true if the user can delete this puzzle, false otherwise
	 */
	public static boolean canDeletePuzzleOrTutorial(UserInfo userInfo, PuzzleInfo puzzleInfo) {
		return canEditPuzzle(userInfo, puzzleInfo) || canEditTutorial(userInfo,puzzleInfo);
	}


	/**
	 * Check if the given user can publish the specified puzzle without review
	 * 
	 * @param userInfo The user that wants to publish a puzzle
	 * @param puzzleInfo The information on the puzzle to publish
	 * @return true if the user can publish this puzzle, false otherwise
	 */
	public static boolean canPublishPuzzleOrTutorialWithoutReview(UserInfo userInfo, PuzzleInfo puzzleInfo) {

		if( userInfo == null )
			return false;

		if( !puzzleInfo.isTutorial() && !puzzleInfo.isValid() )
			return false;
		
		if( userInfo.isAdministrator() && !puzzleInfo.isPublished() ) 
			return true;
		
		return userInfo.getEmail().equals( puzzleInfo.getAuthorEmail() ) &&
				puzzleInfo.getStatus() == Status.PRIVATE;
	}

	/**
	 * Check if the given user can publish the specified puzzle with review
	 * 
	 * @param userInfo The user that wants to publish a puzzle
	 * @param puzzleInfo The information on the puzzle to publish
	 * @return true if the user can publish this puzzle, false otherwise
	 */
	public static boolean canPublishPuzzleOrTutorialAfterReview(UserInfo userInfo, PuzzleInfo puzzleInfo) {

		if( userInfo == null )
			return false;

		if( !puzzleInfo.isTutorial() && !puzzleInfo.isValid() )
			return false;
		
		if( userInfo.isAdministrator() && !puzzleInfo.isPublished() ) 
			return true;
		
		return userInfo.getEmail().equals( puzzleInfo.getAuthorEmail() ) &&
				puzzleInfo.getStatus() == Status.ACCEPTED;
	}
	
	/**
	 * Check if the given user can accept the specified puzzle 
	 * (for now, only administrators can)
	 * 
	 * @param userInfo The user that wants to accept a puzzle
	 * @param puzzleInfo The information on the puzzle to accept
	 * @return true if the user can accept this puzzle, false otherwise
	 */
	public static boolean canAcceptPuzzleOrTutorial(UserInfo userInfo, PuzzleInfo puzzleInfo) {

		if( userInfo == null )
			return false;

		if( !puzzleInfo.isTutorial() && !puzzleInfo.isValid() )
			return false;
		
		if( !userInfo.isAdministrator() ) 
			return false;

		return puzzleInfo.getStatus() == Status.IN_REVIEW;
	}

	/**
	 * Check if the given user can send the specified puzzle to the review 
	 * process
	 * 
	 * @param userInfo The user that wants to send the puzzle for review
	 * @param puzzleInfo The information on the puzzle to send for review
	 * @return true if the user can send this puzzle for review, false otherwise
	 */
	public static boolean canSendPuzzleOrTutorialToReview(UserInfo userInfo, PuzzleInfo puzzleInfo) {

		if( userInfo == null )
			return false;

		if( !puzzleInfo.isTutorial() && !puzzleInfo.isValid() )
			return false;
		
		if( !userInfo.isAdministrator() &&
			!userInfo.getEmail().equals( puzzleInfo.getAuthorEmail() ) ) 
			return false;

		return puzzleInfo.getStatus() == Status.PRIVATE;
	}

	/**
	 * Check if the given user can send the specified puzzle to the review 
	 * process
	 * 
	 * @param userInfo The user that wants to make this puzzle private
	 * @param puzzleInfo The information on the puzzle to make private
	 * @return true if the user can send make this puzzle private, false otherwise
	 */
	public static boolean canMakePuzzleOrTutorialPrivate(UserInfo userInfo, PuzzleInfo puzzleInfo) {
		if( userInfo == null )
			return false;

		if( !userInfo.isAdministrator() ) 
			return false;

		return puzzleInfo.getStatus() != Status.PRIVATE;
	}

	/**
	 * Check if the user can change the status of the specified puzzle to the
	 * desired status
	 * 
	 * @param userInfo The user that wants to change the status of a puzzle
	 * @param status The new desired status for this puzzle
	 * @param puzzleInfo The information on the puzzle on which the status should change
	 * @return true if the user can change this puzzle to the new desired status, false otherwise
	 */
	public static boolean canChangePuzzleOrTutorialStatus(UserInfo userInfo,
			Status status, PuzzleInfo puzzleInfo) {

		if( status == puzzleInfo.getStatus() ) 
			return true;
		
		if( status == Status.PRIVATE )
			return canMakePuzzleOrTutorialPrivate(userInfo, puzzleInfo);
		else if( status == Status.IN_REVIEW )
			return canSendPuzzleOrTutorialToReview(userInfo, puzzleInfo);
		else if( status == Status.ACCEPTED )
			return canAcceptPuzzleOrTutorial(userInfo, puzzleInfo);
		else if( status == Status.PUBLISHED )
			return canPublishPuzzleOrTutorialWithoutReview(userInfo, puzzleInfo);
		else if( status == Status.REVIEW_PUBLISHED )
			return canPublishPuzzleOrTutorialAfterReview(userInfo, puzzleInfo);
		
		return false;
	}



	/**
	 * Return true if the specified user can view an history with the specified characteristics
	 * 
	 * @param userInfo The information about the user
	 * @param puzzleInfo The information about the puzzle attached to this history
	 * @param newHistoryInfo The information about the history to view
	 * @return True if the user is allowed to view this history, false if not
	 */
	public static boolean canViewHistory(UserInfo userInfo, PuzzleInfo puzzleInfo, HistoryInfo historyInfo) {
		String puzzleInfoKey = historyInfo.getPuzzleInfoKey();
		if( puzzleInfoKey == null || !puzzleInfoKey.equals( puzzleInfo.getKey() ) )
			return false;
		
		return historyInfo.isCompleted() && canLoadPuzzle(userInfo, puzzleInfo);
	}
	
	/**
	 * Return true if the specified user can continue an history with the specified characteristics
	 * 
	 * @param userInfo The information about the user
	 * @param puzzleInfo The information about the puzzle attached to this history
	 * @param historyInfo The information about the history to continue
	 * @return True if the user is allowed to continue this history, false if not
	 */
	public static boolean canContinueHistory(UserInfo userInfo, PuzzleInfo puzzleInfo, HistoryInfo historyInfo) {
		String puzzleInfoKey = historyInfo.getPuzzleInfoKey();
		if( puzzleInfoKey == null || !puzzleInfoKey.equals( puzzleInfo.getKey() ) )
			return false;

		if( userInfo == null )
			return false;
		
		if( historyInfo.isCompleted() )
			return false;
		
		if( !canLoadPuzzle(userInfo, puzzleInfo) )
			return false;
		
		if( userInfo.isAdministrator() )
			return true;
		
		return userInfo.getEmail().equals( historyInfo.getAuthorEmail() );
	}
	
	
	/**
	 * Return true if the specified user can load the history or tutorial with the specified characteristics
	 * 
	 * @param userInfo The information about the user
	 * @param puzzleInfo The information about the puzzle attached to this history
	 * @param historyInfo The information about the history to continue
	 * @return True if the user is allowed to continue this history, false if not
	 */
	public static boolean canLoadHistory(UserInfo userInfo, PuzzleInfo puzzleInfo, HistoryInfo historyInfo ) {
		return  canViewHistory(userInfo, puzzleInfo, historyInfo) ||
				canContinueHistory(userInfo, puzzleInfo, historyInfo);
	}

	/**
	 * Return true if the specified user can load the history or tutorial with the specified characteristics
	 * 
	 * @param userInfo The information about the user
	 * @param puzzleInfo The information about the puzzle attached to this history
	 * @param historyInfo The information about the history to continue
	 * @return True if the user is allowed to continue this history, false if not
	 */
	public static boolean canLoadTutorial(UserInfo userInfo, PuzzleInfo puzzleInfo, HistoryInfo historyInfo ) {
		return  canEditTutorial(userInfo, puzzleInfo) ||
				canViewTutorial(userInfo, puzzleInfo);
	}

	/**
	 * Return true if the specified user can create an history with the specified characteristics
	 * 
	 * @param userInfo The information about the user
	 * @param newHistoryInfo The information about the history to create
	 * @return True if the user is allowed to create this history, false if not
	 */
	public static boolean canCreateHistory(UserInfo userInfo, HistoryInfo newHistoryInfo) {
		
		if( userInfo == null )
			return false;
		
		if( userInfo.isAdministrator() )
			return true;
		
		return userInfo.getEmail().equals( newHistoryInfo.getAuthorEmail() );
	}


	/**
	 * Check if the given puzzle can be edited by the given user.
	 * 
	 * IMPORTANT: Keep in sync with CapabilitesServer.canEditFilter
	 * 
	 * @param userInfo The user that should have the right to edit this puzzle
	 * @param puzzleInfo The information on the puzzle to check
	 * 
	 * @return True is the puzzle is editable by this user, false otherwise
	 */
	static public boolean canEditHistory( UserInfo userInfo, HistoryInfo historyInfo ) {
		if( userInfo == null ) return false;
		if( userInfo.isAdministrator() )
			return true;
		return userInfo.getEmail().equals( historyInfo.getAuthorEmail() ) && 
			historyInfo.getDateCompleted() == null;
	}


	/**
	 * Check if the user can request the history list of the specified author
	 * 
	 * @param userInfo The user that makes the request
	 * @param authorEmail The email of the author for which an history list is request
	 * @return True if the request is allowed, false if not
	 */
	public static boolean canRequestHistoryList(UserInfo userInfo, String authorEmail) {
		if( userInfo == null ) return false;
		if( userInfo.isAdministrator() )
			return true;
		return userInfo.getEmail().equals( authorEmail );
	}

	/**
	 * True if the user can list all puzzles in the database, false otherwise
	 * 
	 * @param userInfo  The user to check
	 * @return True if the user can list all puzzles, false otherwise
	 */
	public static boolean canListAllPuzzles(UserInfo userInfo) {
		return userInfo != null && userInfo.isAdministrator();
	}


	/**
	 * True if the user can list editable puzzle, false otherwise
	 * 
	 * @param userInfo  The user to check
	 * @return True if the user can list editable puzzle, false otherwise
	 */
	public static boolean canListEditablePuzzles(UserInfo userInfo) {
		return userInfo != null;
	}

	/**
	 * True if the user can list playable puzzle, false otherwise
	 * 
	 * @param userInfo  The user to check
	 * @return True if the user can list playable puzzle, false otherwise
	 */
	public static boolean canListPlayablePuzzles(UserInfo userInfo) {
		return true;
	}
	
	/**
	 * True if the user can list editable tutorials, false otherwise
	 * 
	 * @param userInfo  The user to check
	 * @return True if the user can list playable tutorials, false otherwise
	 */
	public static boolean canListEditableTutorials(UserInfo userInfo) {
		return userInfo != null;
	}

	/**
	 * True if the user can list viewable tutorials, false otherwise
	 * 
	 * @param userInfo  The user to check
	 * @return True if the user can list viewable tutorials, false otherwise
	 */
	public static boolean canListViewableTutorials(UserInfo userInfo) {
		return true;
	}

	/**
	 * Return true if the specified user can see the information about the user with the
	 * passed email.
	 * 
	 * @param  userInfo The user that wants to access somebody's information
	 * @param email The email of the user to access
	 * @return true if the user can see the information about the passed email, false if not.
	 */
	public static boolean canSeeUser(UserInfo userInfo, String email) {
		
		if( userInfo == null )
			return false;

		if( userInfo.isAdministrator() ) 
			return true;

		return userInfo.getEmail().equals( email );
	
	}
		
	/**
	 * Return true if the specified user can modify the information about the user with the
	 * passed email.
	 * 
	 * @param  userInfo The user that wants to access somebody's information
	 * @param email The email of the user to access
	 * @return true if the user can modify the information about the passed email, false if not.
	 */
	public static boolean canModifyUser(UserInfo userInfo, String email) {
		
		if( userInfo == null )
			return false;

		if( userInfo.isAdministrator() ) 
			return true;

		return userInfo.getEmail().equals( email );
	
	}
	
	/**
	 * True if the user can administrate the website, false otherwise
	 * 
	 * @param userInfo  The user to check
	 * @return True if the user can perform at least some administrative tasks, false otherwise
	 */
	public static boolean canAdministrateSite(UserInfo userInfo) {
		return userInfo != null && userInfo.isAdministrator();
	}

	/**
	 * True if the user can modify information on the website, false otherwise
	 * 
	 * @param userInfo  The user to check
	 * @return True if the user can modify the website information, false otherwise
	 */
	public static boolean canModifySiteInfo(UserInfo userInfo) {
		return userInfo != null && userInfo.isAdministrator();
	}


	/**
	 * True if the user can reset the database, false otherwise
	 * 
	 * @param userInfo  The user to check
	 * @return True if the user can reset the database, false otherwise
	 */
	public static boolean canResetDatabase(UserInfo userInfo) {
		return userInfo != null && userInfo.isAdministrator();
	}


	/**
	 * True if the specified user can have personal solutions to the given puzzle
	 * 
	 * @param puzzleInfo Puzzle to check
	 * @param userInfo User checking if he can have solutions
	 * @return True if the user can have solutions
	 */
	public static boolean canHaveSolutions(PuzzleInfo puzzleInfo,
			UserInfo userInfo) {
		if( puzzleInfo == null || userInfo == null )
			return false;
		if( puzzleInfo.isTutorial() )
			return false;
		
		return true;
	}


	/**
	 * True if the specified user can see solvers of the given puzzle
	 * 
	 * @param puzzleInfo Puzzle to check
	 * @param userInfo User checking if he can see solvers
	 * @return True if the user can see solvers
	 */
	public static boolean canSeeSolvers(PuzzleInfo puzzleInfo, UserInfo userInfo) {
		if( puzzleInfo == null )
			return false;
		if( puzzleInfo.isTutorial() )
			return false;
		
		return true;
	}


	/**
	 * True if the specified user can see statistics on the given puzzle
	 * 
	 * @param puzzleInfo Puzzle to check
	 * @param userInfo User checking if he can see statistics
	 * @return True if the user can see statistics
	 */
	public static boolean canSeeStatistics(PuzzleInfo puzzleInfo,
			UserInfo userInfo) {
		if( puzzleInfo == null || userInfo == null )
			return false;
		if( puzzleInfo.isTutorial() )
			return false;
		
		return true;
	}

	
}
